/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"
#include "test_helper.h"
#include "plugins/ecmascript/runtime/js_array.h"

#include "plugins/ecmascript/runtime/ecma_string.h"
#include "include/coretypes/array.h"
#include "plugins/ecmascript/runtime/js_object.h"
#include "plugins/ecmascript/runtime/js_hclass.h"
#include "plugins/ecmascript/runtime/js_tagged_value-inl.h"
#include "plugins/ecmascript/runtime/js_handle.h"
#include "plugins/ecmascript/runtime/object_operator.h"
#include "include/runtime.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/ecma_vm.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/js_array_iterator.h"
#include "plugins/ecmascript/runtime/js_iterator.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"

using ark::ecmascript::EcmaString;
using ark::ecmascript::IterationKind;
using ark::ecmascript::JSArray;
using ark::ecmascript::JSArrayIterator;
using ark::ecmascript::JSHandle;
using ark::ecmascript::JSIterator;
using ark::ecmascript::JSObject;
using ark::ecmascript::JSTaggedNumber;
using ark::ecmascript::ObjectFactory;
using ark::ecmascript::ObjectOperator;
using ark::ecmascript::PropertyDescriptor;
using ark::ecmascript::TaggedArray;

namespace ark::test {
class JSArrayTest : public testing::Test {
public:
    void SetUp() override
    {
        TestHelper::CreateEcmaVMWithScope(instance_, thread_, scope_);
    }

    void TearDown() override
    {
        TestHelper::DestroyEcmaVMWithScope(instance_, scope_);
    }

    bool SetProperty(ObjectOperator *op, const JSHandle<JSTaggedValue> &value)
    {
        return JSObject::SetProperty(op, value, true);
    }

protected:
    // NOLINTNEXTLINE(misc-non-private-member-variables-in-classes)
    JSThread *thread_ {nullptr};

private:
    PandaVM *instance_ {nullptr};
    EcmaHandleScope *scope_ {nullptr};
};

TEST_F(JSArrayTest, ArrayCreate)
{
    JSHandle<JSTaggedValue> lengthKeyHandle(thread_->GlobalConstants()->GetHandledLengthString());
    auto *arr = JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetObject<JSArray>();
    EXPECT_TRUE(arr != nullptr);
    JSHandle<JSTaggedValue> obj(thread_, arr);
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, lengthKeyHandle).GetValue()->GetInt(), 0);

    // NOLINTNEXTLINE(readability-magic-numbers)
    auto *arr2 = JSArray::ArrayCreate(thread_, JSTaggedNumber(10)).GetObject<JSArray>();
    EXPECT_TRUE(arr2 != nullptr);
    JSHandle<JSTaggedValue> obj2(thread_, arr2);
    EXPECT_EQ(JSArray::GetProperty(thread_, obj2, lengthKeyHandle).GetValue()->GetInt(), 10);
}

TEST_F(JSArrayTest, ArraySpeciesCreate)
{
    JSHandle<JSTaggedValue> lengthKeyHandle(thread_->GlobalConstants()->GetHandledLengthString());
    auto *arr = JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetObject<JSArray>();
    EXPECT_TRUE(arr != nullptr);
    JSHandle<JSObject> obj(thread_, arr);
    EXPECT_EQ(JSArray::GetProperty(thread_, JSHandle<JSTaggedValue>(obj), lengthKeyHandle).GetValue()->GetInt(), 0);

    // NOLINTNEXTLINE(readability-magic-numbers)
    JSArray *arr2 = JSArray::Cast(JSArray::ArraySpeciesCreate(thread_, obj, JSTaggedNumber(10)).GetHeapObject());
    EXPECT_TRUE(arr2 != nullptr);
    JSHandle<JSTaggedValue> obj2(thread_, arr2);
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj2, lengthKeyHandle).GetValue()->GetInt(), 10);
}

TEST_F(JSArrayTest, DefineOwnProperty)
{
    auto ecmaVm = thread_->GetEcmaVM();
    auto factory = ecmaVm->GetFactory();
    JSHandle<JSTaggedValue> lengthKeyHandle(thread_->GlobalConstants()->GetHandledLengthString());
    auto *arr = JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetObject<JSArray>();
    EXPECT_TRUE(arr != nullptr);
    JSHandle<JSTaggedValue> obj(thread_, arr);
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, lengthKeyHandle).GetValue()->GetInt(), 0);

    // NOLINTNEXTLINE(readability-magic-numbers)
    PropertyDescriptor desc(thread_, JSHandle<JSTaggedValue>(thread_, JSTaggedValue(100)), true, true, true);

    EcmaLanguageContext ecmaLanguageContext;
    LanguageContext ctx(&ecmaLanguageContext);
    EcmaString *string1 = *factory->NewFromString("1");
    JSHandle<JSTaggedValue> key1(thread_, static_cast<ObjectHeader *>(string1));
    JSHandle<JSTaggedValue> index1(thread_, JSTaggedValue(1));
    EXPECT_TRUE(JSArray::DefineOwnProperty(thread_, JSHandle<JSObject>(obj), key1, desc));
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, lengthKeyHandle).GetValue()->GetInt(), 2);
    TaggedValue v = JSArray::GetProperty(thread_, obj, key1).GetValue().GetTaggedValue();
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(v.GetInt(), 100);
    v = JSArray::GetProperty(thread_, obj, index1).GetValue().GetTaggedValue();
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(v.GetInt(), 100);
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, lengthKeyHandle).GetValue()->GetInt(), 2);

    EcmaString *string100 = *factory->NewFromString("100");
    JSHandle<JSTaggedValue> key100(thread_, static_cast<ObjectHeader *>(string100));
    // NOLINTNEXTLINE(readability-magic-numbers)
    JSHandle<JSTaggedValue> index100(thread_, JSTaggedValue(100));

    EXPECT_TRUE(JSArray::DefineOwnProperty(thread_, JSHandle<JSObject>(obj), key100, desc));
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, key100).GetValue()->GetInt(), 100);
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, index100).GetValue()->GetInt(), 100);
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, lengthKeyHandle).GetValue()->GetInt(), 101);

    EcmaString *stringx = *factory->NewFromString("2147483646");
    JSHandle<JSTaggedValue> keyx(thread_, static_cast<ObjectHeader *>(stringx));
    // NOLINTNEXTLINE(readability-magic-numbers)
    JSHandle<JSTaggedValue> indexx(thread_, JSTaggedValue(2147483646U));  // 2147483646U

    EXPECT_TRUE(JSArray::DefineOwnProperty(thread_, JSHandle<JSObject>(obj), keyx, desc));
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, keyx).GetValue()->GetInt(), 100);
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, indexx).GetValue()->GetInt(), 100);
    // NOLINTNEXTLINE(readability-magic-numbers)
    EXPECT_EQ(JSArray::GetProperty(thread_, obj, lengthKeyHandle).GetValue()->GetInt(), 2147483647);

    EXPECT_TRUE(JSArray::DeleteProperty(thread_, JSHandle<JSObject>(obj), indexx));
    EXPECT_TRUE(JSArray::GetProperty(thread_, obj, keyx).GetValue()->IsUndefined());
    EXPECT_TRUE(JSArray::GetProperty(thread_, obj, indexx).GetValue()->IsUndefined());
}

TEST_F(JSArrayTest, Next)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<TaggedArray> values(factory->NewTaggedArray(5));
    for (int i = 0; i < 5; i++) {
        values->Set(thread_, i, JSTaggedValue(i));
    }
    JSHandle<JSObject> array(JSArray::CreateArrayFromList(thread_, values));
    JSHandle<JSTaggedValue> iter(factory->NewJSArrayIterator(array, IterationKind::KEY));
    auto ecmaRuntimeCallInfo = TestHelper::CreateEcmaRuntimeCallInfo(thread_, JSTaggedValue::Undefined(), 4);
    ecmaRuntimeCallInfo->SetFunction(JSTaggedValue::Undefined());
    [[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo.get());
    for (int i = 0; i < 5; i++) {
        ecmaRuntimeCallInfo->SetThis(iter.GetTaggedValue());
        JSTaggedValue ret = JSArrayIterator::Next(ecmaRuntimeCallInfo.get());
        JSHandle<JSTaggedValue> result(thread_, ret);
        EXPECT_EQ(JSIterator::IteratorValue(thread_, result)->GetInt(), i);
    }
    TestHelper::TearDownFrame(thread_, prev);
}

TEST_F(JSArrayTest, Iterator)
{
    ObjectFactory *factory = thread_->GetEcmaVM()->GetFactory();
    JSHandle<TaggedArray> values(factory->NewTaggedArray(5));
    for (int i = 0; i < 5; i++) {
        values->Set(thread_, i, JSTaggedValue(i));
    }
    JSHandle<JSObject> array(JSArray::CreateArrayFromList(thread_, values));
    JSHandle<JSTaggedValue> keyIter(factory->NewJSArrayIterator(array, IterationKind::KEY));
    JSHandle<JSTaggedValue> valueIter(factory->NewJSArrayIterator(array, IterationKind::VALUE));
    JSHandle<JSTaggedValue> iter(factory->NewJSArrayIterator(array, IterationKind::KEY_AND_VALUE));

    for (int i = 0; i < 5; i++) {
        if (i == 2) {
            JSHandle<JSTaggedValue> key(thread_, JSTaggedValue(i));
            JSObject::DeleteProperty(thread_, JSHandle<JSObject>(array), key);
        }
        JSHandle<JSTaggedValue> keyResult(JSIterator::IteratorStep(thread_, keyIter));
        JSHandle<JSTaggedValue> valueResult(JSIterator::IteratorStep(thread_, valueIter));
        JSHandle<JSTaggedValue> iterResult(JSIterator::IteratorStep(thread_, iter));
        JSHandle<JSTaggedValue> iterValue(JSIterator::IteratorValue(thread_, iterResult));
        JSHandle<JSTaggedValue> indexKey(thread_, JSTaggedValue(0));
        JSHandle<JSTaggedValue> elementKey(thread_, JSTaggedValue(1));
        if (i == 2) {
            EXPECT_EQ(i, JSIterator::IteratorValue(thread_, keyResult)->GetInt());
            EXPECT_EQ(JSTaggedValue::Undefined(), JSIterator::IteratorValue(thread_, valueResult).GetTaggedValue());
            EXPECT_EQ(i, JSObject::GetProperty(thread_, iterValue, indexKey).GetValue()->GetInt());
            EXPECT_EQ(JSTaggedValue::Undefined(),
                      JSObject::GetProperty(thread_, iterValue, elementKey).GetValue().GetTaggedValue());
            continue;
        }
        EXPECT_EQ(i, JSIterator::IteratorValue(thread_, keyResult)->GetInt());
        EXPECT_EQ(i, JSIterator::IteratorValue(thread_, valueResult)->GetInt());
        EXPECT_EQ(i, JSObject::GetProperty(thread_, iterValue, indexKey).GetValue()->GetInt());
        EXPECT_EQ(i, JSObject::GetProperty(thread_, iterValue, elementKey).GetValue()->GetInt());
    }
}

TEST_F(JSArrayTest, AddProperty)
{
    auto *arr = JSArray::ArrayCreate(thread_, JSTaggedNumber(0)).GetObject<JSArray>();
    ASSERT_TRUE(arr != nullptr);
    JSHandle<JSTaggedValue> obj(thread_, arr);
    JSHandle<JSTaggedValue> key(thread_, JSTaggedValue(1));
    JSHandle<JSTaggedValue> value(thread_, JSTaggedValue(true));

    ObjectOperator op(thread_, obj, key);
    ASSERT_TRUE(SetProperty(&op, value));
    ASSERT_TRUE(op.IsFound());
    ASSERT_TRUE(op.IsFastMode());
}

}  // namespace ark::test
